<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>文件上传</title>
  </head>
  <script src="./axios.min.js"></script>
  <script src="./md5.min.js"></script>
  <script src="./qs.js"></script>
  <script src="./spark-md5.min.js"></script>
  <body>
    <h2>上传文件</h2>
    <form id="uploadForm" enctype="multipart/form-data">
      <label for="fileInput">选择文件:</label>
      <input type="file" id="fileInput" name="file" multiple />
      <br /><br />
      <button type="button" onclick="getFile()">上传文件</button>
      <div>
        <span>当前上传进度：</span>
        <span id="percent"></span>
      </div>
    </form>

    <script>
      function loadNextChunk(file, currentChunk, chunkSize, fileReader) {
        const start = currentChunk * chunkSize;
        const end = Math.min(start + chunkSize, file.size);
        fileReader.readAsArrayBuffer(file.slice(start, end));
      }

      function calculateMD5(file) {
        return new Promise((resolve, reject) => {
          const chunkSize = 20 * 1024; // 20KB chunks
          const spark = new SparkMD5.ArrayBuffer();
          const fileReader = new FileReader();
          let currentChunk = 0;
          const chunks = Math.ceil(file.size / chunkSize);

          fileReader.onload = function (e) {
            spark.append(e.target.result);
            currentChunk++;
            if (currentChunk < chunks) {
              loadNextChunk(file, currentChunk, chunkSize, fileReader);
            } else {
              resolve(spark.end());
            }
          };

          loadNextChunk(file, currentChunk, chunkSize, fileReader);
        });
      }
    </script>

    <script>
      // 计算文件的SHA-256哈希值
      function calculateFileHash(file) {
        const reader = new FileReader();

        // 使用Promise处理异步操作
        return new Promise((resolve, reject) => {
          reader.onload = function (event) {
            // 获取文件的ArrayBuffer
            const fileBuffer = event.target.result;

            // 使用SubtleCrypto API计算SHA-256哈希
            crypto.subtle
              .digest("SHA-256", fileBuffer)
              .then((hashBuffer) => {
                // 将哈希Buffer转为十六进制字符串
                const hashArray = Array.from(new Uint8Array(hashBuffer));
                const hashHex = hashArray
                  .map((byte) => byte.toString(16).padStart(2, "0"))
                  .join("");
                resolve(hashHex);
              })
              .catch(reject);
          };

          reader.onerror = reject;
          reader.readAsArrayBuffer(file); // 以ArrayBuffer方式读取文件
        });
      }
    </script>

    <script>
      const chunkSize = 1024 * 20; // 20KB
      let currentChunk = 0;

      // 分片
      function getSlices(file) {
        const slices = [];

        for (let i = 0; i < file.size; i += chunkSize) {
          const blob = file.slice(i, Math.min(i + chunkSize, file.size));
          slices.push(blob);
        }

        return slices;
      }
    </script>

    <script>
      async function uploadSlices(slices, file, cacheObj, hash) {
        let serviceIndex = 0;
        let uploadType = "start";
        for (let i = 0, len = slices.length; i < len; i++) {
          if (i > 0 && i < len - 1) {
            uploadType = "uploading";
          } else if (i === len - 1) {
            uploadType = "end";
          }
          if (serviceIndex > i) continue;

          const formData = new FormData();
          formData.append("file", slices[i]); // 切片
          formData.append("groupId", cacheObj.groupId); // 文件组ID
          formData.append("fileId", cacheObj.fileId); // 文件ID
          formData.append("index", i); // 上传切片的顺序
          formData.append("name", file.name); // 文件名称
          formData.append("chunkSize", chunkSize); // 切片大小
          formData.append("uploadType", uploadType); // 上传操作类型， start, uploading, end

          const res = await axios.post(`/file/slice`, formData, {
            headers: {
              "Content-Type": "multipart/form-data", // 指定表单数据类型
            },
          });

          if (res.data.code === 0 && res.data.data.position) {
            serviceIndex = Math.ceil(
              Number(res.data.data.position) / chunkSize
            );
          } else {
            serviceIndex++;
          }

          console.log("serviceIndex", serviceIndex);

          document.getElementById("percent").innerHTML =
            (Math.min((serviceIndex * chunkSize) / file.size, 1) * 100).toFixed(
              2
            ) + "%";

          if (uploadType === "end") {
            window.localStorage.removeItem(hash);
          }

          console.log(`finish-${i}`, res);
        }
      }

      async function getFile() {
        // 获取文件输入框
        const fileInput = document.getElementById("fileInput");

        const groupId = Date.now();
        // 获取用户选择的文件列表
        const files = fileInput.files;

        const file = files[0];
        const hash = await calculateMD5(file);

        let cacheObj = null;
        if (!window.localStorage.getItem(hash)) {
          cacheObj = {
            fileId: Date.now(),
            groupId,
            name: file.name,
          };
          window.localStorage.setItem(hash, JSON.stringify(cacheObj));
        } else {
          cacheObj = JSON.parse(window.localStorage.getItem(hash));
        }

        const slices = getSlices(file);

        await uploadSlices(slices, file, cacheObj, hash);
      }
    </script>
  </body>
</html>
